package de.fhin.mas.agents.searcher.behaviours;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.SimpleBehaviour;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.NotUnderstoodException;
import jade.domain.FIPAAgentManagement.RefuseException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.proto.AchieveREResponder;
import de.fhin.mas.MASEvents;
import de.fhin.mas.agents.searcher.SearcherAgent;
import de.fhin.mas.util.MessageUtil;

/**
 * Dieses <tt>Behaviour</tt> behandelt alle eingehenden Anfragen, einen
 * SystemStopp durchzuführen gemäß dem FIPA Request Protokoll.
 * 
 * @author Alexander Weickmann
 */
public class SystemStopRequiredRequestResponder extends AchieveREResponder {

	/** Die eindeutige ID zur Serialisierung ueber das Netzwerk. */
	private static final long serialVersionUID = 2167485472497638474L;

	/**
	 * Erzeugt einen <tt>SystemStopRequiredRequestResponder</tt>.
	 * 
	 * @param a
	 *            Der Agent, zu dem das <tt>Behaviour</tt> gehört.
	 */
	public SystemStopRequiredRequestResponder(Agent a) {
		super(a, MessageUtil.buildBasicCommunicationTemplate(FIPANames.InteractionProtocol.FIPA_REQUEST, MASEvents.SYSTEM_STOP_REQUIRED.getName(),
				ACLMessage.REQUEST, null));
		registerPrepareResultNotification(new WaitingForNavigatorToStop());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected ACLMessage prepareResponse(ACLMessage request) throws NotUnderstoodException, RefuseException {

		ACLMessage agree = request.createReply();
		agree.setPerformative(ACLMessage.AGREE);

		return agree;
	}

	/**
	 * Dieses <tt>Behaviour</tt> wartet so lange bis der Navigator ein
	 * <tt>INFORM</tt> meldet und stoppt dann die Zielsuche sowie den
	 * Umgebungsscan.
	 * <p>
	 * Sollte der Navigator dafür länger als das Timeout brauchen wird ein
	 * <tt>FAILURE</tt> geschickt.
	 * <p>
	 * Sollte kein Navigator vorhanden sein wird direkt angehalten.
	 */
	private class WaitingForNavigatorToStop extends SimpleBehaviour {

		/** Die eindeutige ID zur Serialisierung ueber das Netzwerk. */
		private static final long serialVersionUID = 7796081798067450875L;

		/** Zeit, die wir auf den Stopp Navigator warten. */
		private static final long TIMEOUT = 10000; // 10 Sekunden

		/** Statusflag, welches anzeigt ob das <tt>Behaviour</tt> fertig ist. */
		private boolean finished;

		/** Beginn der Zeitmessung fuer das Timeout. */
		private long startTime;

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void action() {
			finished = false;

			if (checkAndHandleTimeout()) {
				// FAILURE
				ACLMessage request = (ACLMessage) getDataStore().get(REQUEST_KEY);
				ACLMessage failure = request.createReply();
				failure.setPerformative(ACLMessage.FAILURE);
				failure.setContent("wait-time for navigator to stop over");
				getDataStore().put(RESULT_NOTIFICATION_KEY, failure);
				return;
			}

			// Schauen ob ein Navigator da ist
			AID possibleNavigator = ((SearcherAgent) myAgent).getNavigator();
			if (possibleNavigator != null) {
				/*
				 * Navigator vorhanden, also auf das INFORM des Navigators
				 * warten
				 */
				MessageTemplate informTemplate = MessageUtil.buildBasicCommunicationTemplate(FIPANames.InteractionProtocol.FIPA_REQUEST,
						MASEvents.SYSTEM_STOP_REQUIRED.getName(), ACLMessage.INFORM, possibleNavigator);
				ACLMessage possibleInform = myAgent.receive(informTemplate);
				if (possibleInform != null) {
					stop();
				}

			} else {
				// Kein Navigator vorhanden, wir können also direkt stoppen
				stop();
			}

			// Falls noch nicht gestoppt weiter auf Nachrichten warten
			block(500); // Nur 0,5 Sek blocken um Timeout wieder zu prüfen
		}

		/**
		 * Kuemmert sich um den Timeout, gibt <tt>true</tt> zurueck, falls der
		 * Timeout abgelaufen ist, <tt>false</tt> falls noch nicht.
		 */
		private boolean checkAndHandleTimeout() {
			// Initiales setzen für Timeout
			if (startTime == 0) {
				startTime = System.currentTimeMillis();
			}
			// Timeout prüfen
			if (System.currentTimeMillis() > startTime + TIMEOUT) {
				// Timeout abgelaufen
				finished = true;
				return true;
			}

			// Timeout noch nicht abgelaufen
			return false;
		}

		/**
		 * Fuehrt den Stopp durch.
		 */
		private void stop() {
			// Stoppen
			SearcherAgent searcherAgent = (SearcherAgent) myAgent;
			searcherAgent.pauseTargetSearch();
			searcherAgent.cancelEnvironmentScan();

			// INFORM
			ACLMessage request = (ACLMessage) getDataStore().get(REQUEST_KEY);
			ACLMessage inform = request.createReply();
			inform.setPerformative(ACLMessage.INFORM);
			getDataStore().put(RESULT_NOTIFICATION_KEY, inform);

			finished = true;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public int onEnd() {
			// Timeout für künftige Requests zurücksetzen
			startTime = 0;

			return super.onEnd();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean done() {
			return finished;
		}

	}

}
